/**
 * Copyright (c) 2022 Presto Labs Pte. Ltd.
 * Author: Ju
 */
#pragma once

#include <cassert>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <list>
#include <map>
#include <set>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

namespace coin2::base {

class Unpacker {
 public:
  Unpacker(const char *buf, uint32_t len, bool init = true);
  Unpacker(const void *buf, uint32_t len, bool init = true);

  ~Unpacker();
  void Reset();

  bool PopBoolean();
  uint8_t PopUint8();
  uint16_t PopUint16();
  uint32_t PopUint32();
  uint64_t PopUint64();
  float PopFloat();
  double PopDouble();
  std::string PopString();
  std::vector<uint8_t> PopBuffer();
  void PopBinary(void *buf, size_t length);

  std::vector<uint8_t> PopBuffer2();

  const uint8_t *Buffer() const;
  uint32_t Length() const;

 private:
  void Initialize();
  void CheckSize(uint32_t more) const;
  void MemCopy(void *dest, uint32_t len, bool move = true);

  struct RawBuffer {
    const uint32_t length_;
    const uint8_t *buffer_;
  };
  RawBuffer data_;
  uint32_t position_;
};

inline void Unpacker::Reset() { position_ = 0; }

inline void Unpacker::MemCopy(void *dest, uint32_t len, bool move) {
  ::memcpy(dest, data_.buffer_ + position_, len);
  if (move) {
    position_ += len;
  }
}

inline uint32_t Unpacker::PopUint32() {
  uint32_t v = 0;
  CheckSize(sizeof(v));
  MemCopy(&v, sizeof(v));
  return v;
}

inline uint16_t Unpacker::PopUint16() {
  uint16_t v = 0;
  CheckSize(sizeof(v));
  MemCopy(&v, sizeof(v));
  return v;
}

inline bool Unpacker::PopBoolean() {
  bool v = false;
  CheckSize(sizeof(v));
  MemCopy(&v, sizeof(v));
  return v;
}

inline uint8_t Unpacker::PopUint8() {
  uint8_t v = 0;
  CheckSize(sizeof(v));
  MemCopy(&v, sizeof(v));
  return v;
}

inline uint64_t Unpacker::PopUint64() {
  uint64_t v = 0;
  CheckSize(sizeof(v));
  MemCopy(&v, sizeof(v));
  return v;
}

inline float Unpacker::PopFloat() {
  float v = 0;
  CheckSize(sizeof(v));
  MemCopy(&v, sizeof(v));
  return v;
}

inline double Unpacker::PopDouble() {
  double v = 0;
  CheckSize(sizeof(v));
  MemCopy(&v, sizeof(v));
  return v;
}

inline std::string Unpacker::PopString() {
  uint32_t length = PopUint32();
  CheckSize(length);
  std::string s;
  s.resize(length);
  MemCopy(&s[0], length);
  return s;
}

inline std::vector<uint8_t> Unpacker::PopBuffer() {
  uint32_t length = PopUint32();
  CheckSize(length);
  std::vector<uint8_t> s;
  s.resize(length);
  MemCopy(s.data(), length);
  return s;
}
// pop raw buffer
inline std::vector<uint8_t> Unpacker::PopBuffer2() {
  uint32_t length = PopUint32() - 4;
  CheckSize(length);
  std::vector<uint8_t> s;
  s.resize(length);
  MemCopy(s.data(), length);
  return s;
}

inline void Unpacker::PopBinary(void *buf, size_t length) {
  CheckSize(length);
  MemCopy(buf, length);
}

inline const uint8_t *Unpacker::Buffer() const { return data_.buffer_; }

inline uint32_t Unpacker::Length() const { return data_.length_; }

inline void Unpacker::CheckSize(uint32_t more) const {
  if (data_.length_ < position_ + more) {
    throw std::underflow_error("Unpacker buffer underflow!");
  }
}

inline Unpacker &operator>>(Unpacker &p, char &v) {
  v = static_cast<char>(p.PopUint8());
  return p;
}

inline Unpacker &operator>>(Unpacker &p, uint8_t &v) {
  v = p.PopUint8();
  return p;
}

inline Unpacker &operator>>(Unpacker &p, uint16_t &v) {
  v = p.PopUint16();
  return p;
}

inline Unpacker &operator>>(Unpacker &p, uint32_t &v) {
  v = p.PopUint32();
  return p;
}

inline Unpacker &operator>>(Unpacker &p, uint64_t &v) {
  v = p.PopUint64();
  return p;
}

inline Unpacker &operator>>(Unpacker &p, int8_t &v) {
  v = static_cast<int8_t>(p.PopUint8());
  return p;
}

inline Unpacker &operator>>(Unpacker &p, int16_t &v) {
  v = static_cast<int16_t>(p.PopUint16());
  return p;
}

inline Unpacker &operator>>(Unpacker &p, int32_t &v) {
  v = static_cast<int32_t>(p.PopUint32());
  return p;
}

inline Unpacker &operator>>(Unpacker &p, int64_t &v) {
  v = static_cast<int64_t>(p.PopUint64());
  return p;
}

inline Unpacker &operator>>(Unpacker &p, float &v) {
  v = static_cast<float>(p.PopFloat());
  return p;
}

inline Unpacker &operator>>(Unpacker &p, double &v) {
  v = static_cast<double>(p.PopDouble());
  return p;
}

inline Unpacker &operator>>(Unpacker &p, std::string &s) {
  s = p.PopString();
  return p;
}

inline Unpacker &operator>>(Unpacker &p, std::vector<uint8_t> &s) {
  s = p.PopBuffer();
  return p;
}

template <typename T>
inline Unpacker &operator>>(Unpacker &p, std::vector<T> &v) {
  uint32_t count = p.PopUint32();
  for (uint32_t i = 0; i < count; ++i) {
    T t;
    p >> t;
    v.push_back(std::move(t));
  }

  return p;
}

template <typename T>
inline Unpacker &operator>>(Unpacker &p, std::list<T> &v) {
  uint32_t count = p.PopUint32();
  for (uint32_t i = 0; i < count; ++i) {
    T t;
    p >> t;
    v.push_back(std::move(t));
  }

  return p;
}

template <typename K, typename V>
inline Unpacker &operator>>(Unpacker &p, std::pair<K, V> &v) {
  p >> v.first >> v.second;
  return p;
}

template <typename T>
inline Unpacker &operator>>(Unpacker &p, std::set<T> &v) {
  uint32_t count = p.PopUint32();
  for (uint32_t i = 0; i < count; ++i) {
    T t;
    p >> t;
    v.insert(std::move(t));
  }

  return p;
}

template <typename K, typename V>
inline Unpacker &operator>>(Unpacker &p, std::map<K, V> &x) {
  uint32_t count = p.PopUint32();
  for (uint32_t i = 0; i < count; ++i) {
    K k;
    V v;
    p >> k >> v;
    x.insert(std::make_pair(std::move(k), std::move(v)));
  }

  return p;
}

template <typename K, typename V>
inline Unpacker &operator>>(Unpacker &p, std::unordered_map<K, V> &x) {
  uint32_t count = p.PopUint32();
  for (uint32_t i = 0; i < count; ++i) {
    K k;
    V v;
    p >> k >> v;
    x.insert(std::make_pair(std::move(k), std::move(v)));
  }

  return p;
}

template <typename T, size_t Len>
inline Unpacker &operator>>(Unpacker &p, std::array<T, Len> &arr) {
  for (size_t i = 0; i < Len; i++) {
    p >> arr[i];
  }
  return p;
}

template <typename K>
inline Unpacker &operator>>(Unpacker &p, std::unordered_set<K> &s) {
  uint32_t count = p.PopUint32();
  for (uint32_t i = 0; i < count; ++i) {
    K k;
    p >> k;
    s.emplace(std::move(k));
  }

  return p;
}

}  // namespace coin2::base
